package com.yanggu.jt808.model.base;

import com.yanggu.jt808.consts.Jt808Constant;
import com.yanggu.jt808.util.BCD;
import com.yanggu.jt808.util.Bit;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;


/**
 * @author bo
 */
@Data
@NoArgsConstructor
@Slf4j
public class DataPackage {

    /**
     * 消息头
     */
    protected Header header = new Header();

    /**
     * 消息体
     */
    protected ByteBuf msgBody;

    public DataPackage(ByteBuf msgBody) {
        this.msgBody = msgBody;
    }

    public DataPackage(DataPackage dataPackage) {
        this.msgBody = dataPackage.getMsgBody();
        this.header = dataPackage.getHeader();
    }

    /**
     * 解析头部
     */
    public void parseHeader() {
        //1. 解析消息头
        //获取消息ID(2个字节)
        int msgId = msgBody.readUnsignedShort();
        //将10进制的msgId转换成16进制
        msgId = Integer.parseInt(Integer.toHexString(msgId), 16);
        log.info("解析头部---msgId:{}", msgId);

        header.setMsgId(msgId);
        //获取消息体属性(2个字节)
        header.setMsgBodyProps(msgBody.readUnsignedShort());
        //设置是否加密
        header.setEncryptionType((byte) ((header.getMsgBodyProps() & 0x1c00) >> 10));
        //设置是否是2019年版本
        header.setIfVersion2019(((header.getMsgBodyProps() & 0x4000) >> 14) == 1);
        if (header.isIfVersion2019()) {
            //2019年协议新增协议版本号(1个字节)
            header.setProtocolVersion(msgBody.readUnsignedByte());
            //2019年协议终端手机号为10个字节
            byte[] bcd = readBytes(10);
            String terminalPhone = BCD.bcdToString(bcd);
            header.setTerminalPhone(terminalPhone);
            log.info("2019版本---terminalPhone:{}", terminalPhone);

        } else {
            //2011、2013年协议终端手机号为6个字节
            byte[] bcd = readBytes(6);
            String terminalPhone = BCD.bcdToString(bcd);
            header.setTerminalPhone(terminalPhone);
            log.info("2011、2013年版本---terminalPhone:{}", terminalPhone);
        }
        //获取终端的消息流水号(2个字节)
        header.setFlowId(msgBody.readUnsignedShort());
        //是否有分包
        boolean subPackage = ((header.getMsgBodyProps() & 0x2000) >> 13) == 1;
        if (subPackage) {
            header.setSubPackage(true);
            //消息总包数(2个字节)
            header.setTotalPackage(msgBody.readUnsignedShort());
            //包序号(2个字节)
            header.setPackageNumber(msgBody.readUnsignedShort());
        }
        //2. 验证包体长度
        //消息头中消息体属性消息长度是否等于剩余可读字节
        if (header.getMsgBodyLength() != msgBody.readableBytes()) {
            throw new RuntimeException("包体长度有误");
        }
    }

    /**
     * 消息内容解析
     */
    protected void parseBody() {
        //子类需重写此方法

    }

    /**
     * 转换为字节数组
     *
     * @return
     */
    protected byte[] toBytes() {
        return ByteBufUtil.getBytes(msgBody);
    }


    /**
     * 响应报文重写 并调用父类
     *
     * @return
     */
    public ByteBuf toByteBufMsg() {
        //在JT808Encoder escape()方法处回收
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();
        //设置消息ID
        byteBuf.writeShort(header.getMsgId());

        //是否分包
        boolean subPackage = header.isSubPackage();
        int subPkg = subPackage ? 1 : 0;
        //是否是2019年版本
        boolean version2019 = header.isIfVersion2019();
        int is2019 = version2019 ? 1 : 0;
        //消息体长度
        int msgLength = msgBody.readableBytes();
        //构建消息体属性(消息体长度、数据加密方式、分包、是否为2019年协议、保留字段)
        short msgBodyProps = (short) (((msgLength & 0x3FF) | (header.getEncryptionType() << 10 & 0x1C00) | ((subPkg << 13) & 0x2000) | ((is2019 << 14) & 0xC000)) & 0xffff);
        //设置消息体属性
        byteBuf.writeShort(msgBodyProps);
        if (version2019) {
            byteBuf.writeByte(header.getProtocolVersion());
        }
        //需要转换成BCD码, 6个字节或者10个字节
        // StringUtils.leftPad(this.header.getTerminalPhone(), 12, "0");不足12位的前面填充0

        byteBuf.writeBytes(BCD.toBcdBytes(header.getTerminalPhone()));
        //设置消息流水号(2个字节)
        byteBuf.writeShort(header.getFlowId());
        //根据是否分包设置总包数和包序号(分贝时2个字节)
        if (header.isSubPackage()) {
            byteBuf.writeShort(header.getTotalPackage());
            byteBuf.writeShort(header.getPackageNumber());
        }
        // //写入消息体
        byteBuf.writeBytes(this.msgBody);
        return byteBuf;
    }


    /**
     * 从ByteBuf中read固定长度的数组,相当于ByteBuf.readBytes(byte[] dst)的简单封装
     *
     * @param length
     * @return
     */
    protected byte[] readBytes(int length) {
        byte[] bytes = new byte[length];
        msgBody.readBytes(bytes);
        return bytes;
    }

    /**
     * 从ByteBuf中读出固定长度的数组 ，根据808默认字符集构建字符串
     *
     * @param length
     * @return
     */
    public String readString(int length) {
        return new String(readBytes(length), Jt808Constant.DEFAULT_CHARSET);
    }

    /**
     * 解析为String
     *
     * @param data
     * @param startIndex
     * @param lenth
     * @return
     */
    protected String parseStringFromBytes(byte[] data, int startIndex, int lenth) {
        return this.parseStringFromBytes(data, startIndex, lenth, null);
    }

    protected String parseStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return new String(tmp, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("解析字符串出错:{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }

    /**
     * @param data
     * @param startIndex
     * @param lenth
     * @return
     */
    protected byte[] parseBytesFromBytes(byte[] data, int startIndex, int lenth) {
        byte[] tmp = null;
        try {
            if (lenth == -1) {
                tmp = new byte[data.length - startIndex];
            } else {
                tmp = new byte[lenth];
            }
            System.arraycopy(data, startIndex, tmp, 0, tmp.length);
        } catch (Exception e) {
            log.error("解析字节数组出错:{}", e.getMessage());
            e.printStackTrace();
        }
        return tmp;
    }

    /**
     * 解析为BCD编码
     *
     * @param data
     * @param startIndex
     * @param lenth
     * @return
     */
    protected String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth) {
        return this.parseBcdStringFromBytes(data, startIndex, lenth, null);
    }

    protected String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return BCD.BCDtoString(tmp);
        } catch (Exception e) {
            log.error("解析BCD(8421码)出错:{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }

    /**
     * 解析为int
     *
     * @param data
     * @param startIndex
     * @param length
     * @return
     */
    protected int parseIntFromBytes(byte[] data, int startIndex, int length) {
        return this.parseIntFromBytes(data, startIndex, length, 0);
    }

    protected int parseIntFromBytes(byte[] data, int startIndex, int length, int defaultVal) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return Bit.byteToInteger(tmp);
        } catch (Exception e) {
            log.error("解析整数出错:{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }

    public DataPackage copyBy(DataPackage request,int flowId) {
        this.getHeader().setTerminalPhone(request.getHeader().getTerminalPhone());
        this.getHeader().setProtocolVersion(request.getHeader().getProtocolVersion());
        this.getHeader().setIfVersion2019(request.getHeader().isIfVersion2019());

        //设置消息流水号(平台生成)
        this.getHeader().setFlowId(flowId);
        return this;
    }


}
